package controllers

import (
	"encoding/json"
	"html/template"
	"net/http"
	"obsidian-plugin-server/models"
	"regexp"
	"sort"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/russross/blackfriday/v2"
	"gopkg.in/yaml.v3"
	"gorm.io/gorm"
)

type WebController struct {
	DB *gorm.DB
}

// DashboardData 仪表盘页面数据
type DashboardData struct {
	Title          string
	UserCount      int64
	DocumentCount  int64
	UserDatesJSON  template.JS
	UserCountsJSON template.JS
	DocDatesJSON   template.JS
	DocCountsJSON  template.JS
}

// Frontmatter 用于解析 YAML frontmatter
type Frontmatter struct {
	Title string   `yaml:"title"`
	Tags  []string `yaml:"tags"`
}

// parseFrontmatter 解析 YAML frontmatter，返回 frontmatter 和去掉 frontmatter 的正文
func parseFrontmatter(content string) (Frontmatter, string) {
	var frontmatter Frontmatter

	re := regexp.MustCompile(`^---\s*\n([\s\S]*?)\n---\s*\n`)
	matches := re.FindStringSubmatch(content)

	if len(matches) > 1 {
		if err := yaml.Unmarshal([]byte(matches[1]), &frontmatter); err != nil {
			return Frontmatter{}, content
		}
		return frontmatter, strings.TrimPrefix(content, matches[0])
	}

	return Frontmatter{}, content
}

// renderMarkdown 渲染 Markdown 为 HTML，并处理 mermaid 和代码块
func renderMarkdown(content string) template.HTML {
	extensions := blackfriday.CommonExtensions | blackfriday.FencedCode | blackfriday.NoEmptyLineBeforeBlock

	htmlBytes := blackfriday.Run([]byte(content), blackfriday.WithExtensions(extensions))
	html := string(htmlBytes)

	html = processMermaidBlocks(html)
	html = processCodeBlocks(html)

	return template.HTML(html)
}

// processMermaidBlocks 处理 ```mermaid 代码块
func processMermaidBlocks(html string) string {
	re := regexp.MustCompile(`<pre><code class="language-mermaid">([\s\S]*?)</code></pre>`)

	return re.ReplaceAllStringFunc(html, func(match string) string {
		codeRe := regexp.MustCompile(`<pre><code class="language-mermaid">([\s\S]*?)</code></pre>`)
		codeMatches := codeRe.FindStringSubmatch(match)

		if len(codeMatches) > 1 {
			mermaidCode := strings.TrimSpace(codeMatches[1])
			return `<div class="mermaid-wrapper"><div class="mermaid">` + mermaidCode + `</div></div>`
		}

		return match
	})
}

// processCodeBlocks 处理普通代码块
func processCodeBlocks(html string) string {
	re := regexp.MustCompile(`<pre><code class="language-(\w+)">([\s\S]*?)</code></pre>`)

	return re.ReplaceAllStringFunc(html, func(match string) string {
		codeRe := regexp.MustCompile(`<pre><code class="language-(\w+)">([\s\S]*?)</code></pre>`)
		codeMatches := codeRe.FindStringSubmatch(match)

		if len(codeMatches) > 2 {
			language := codeMatches[1]
			code := codeMatches[2]

			if language != "mermaid" {
				return `<pre><code class="language-` + language + `">` + code + `</code></pre>`
			}
		}

		return match
	})
}

// DocumentViewData 文档查看页面数据
type DocumentViewData struct {
	Title            string
	UUID             string
	Author           string
	ContentHTML      template.HTML
	Views            int
	CreatedAt        string
	ExpiresAt        *string
	IsExpired        bool
	ShowPasswordForm bool
	ErrorMessage     string
	Theme            string
	UserUID          string
	Tags             []string
}

// DocumentWithDir 用于在列表页面展示目录信息
type DocumentWithDir struct {
	models.Document
	DirPath string
}

// DirectoryGroup 用于左侧目录分组展示
type DirectoryGroup struct {
	DirPath   string
	Documents []DocumentWithDir
}

// Dashboard 仪表盘页面：展示用户数、文档数和每日新增折线图
func (wc *WebController) Dashboard(c *gin.Context) {
	var userCount int64
	var documentCount int64

	if err := wc.DB.Model(&models.User{}).Count(&userCount).Error; err != nil {
		c.HTML(http.StatusInternalServerError, "error.html", gin.H{
			"Title":   "服务器错误",
			"Message": "统计用户数量失败",
		})
		return
	}

	if err := wc.DB.Model(&models.Document{}).Count(&documentCount).Error; err != nil {
		c.HTML(http.StatusInternalServerError, "error.html", gin.H{
			"Title":   "服务器错误",
			"Message": "统计文档数量失败",
		})
		return
	}

	type dailyStat struct {
		Date  string
		Count int64
	}

	var userStats []dailyStat
	if err := wc.DB.
		Model(&models.User{}).
		Select("strftime('%Y-%m-%d', created_at) as date, COUNT(*) as count").
		Group("date").
		Order("date").
		Scan(&userStats).Error; err != nil {
		c.HTML(http.StatusInternalServerError, "error.html", gin.H{
			"Title":   "服务器错误",
			"Message": "统计每日新增用户失败",
		})
		return
	}

	var docStats []dailyStat
	if err := wc.DB.
		Model(&models.Document{}).
		Select("strftime('%Y-%m-%d', created_at) as date, COUNT(*) as count").
		Group("date").
		Order("date").
		Scan(&docStats).Error; err != nil {
		c.HTML(http.StatusInternalServerError, "error.html", gin.H{
			"Title":   "服务器错误",
			"Message": "统计每日新增文档失败",
		})
		return
	}

	userDates := make([]string, 0, len(userStats))
	userCounts := make([]int64, 0, len(userStats))
	for _, s := range userStats {
		userDates = append(userDates, s.Date)
		userCounts = append(userCounts, s.Count)
	}

	docDates := make([]string, 0, len(docStats))
	docCounts := make([]int64, 0, len(docStats))
	for _, s := range docStats {
		docDates = append(docDates, s.Date)
		docCounts = append(docCounts, s.Count)
	}

	userDatesJSONBytes, _ := json.Marshal(userDates)
	userCountsJSONBytes, _ := json.Marshal(userCounts)
	docDatesJSONBytes, _ := json.Marshal(docDates)
	docCountsJSONBytes, _ := json.Marshal(docCounts)

	data := DashboardData{
		Title:          "仪表盘 - 文档分享统计",
		UserCount:      userCount,
		DocumentCount:  documentCount,
		UserDatesJSON:  template.JS(userDatesJSONBytes),
		UserCountsJSON: template.JS(userCountsJSONBytes),
		DocDatesJSON:   template.JS(docDatesJSONBytes),
		DocCountsJSON:  template.JS(docCountsJSONBytes),
	}

	c.HTML(http.StatusOK, "dashboard.html", data)
}

// ShowDocument 显示文档页面 /d/:uuid
func (wc *WebController) ShowDocument(c *gin.Context) {
	uuid := c.Param("uuid")

	var document models.Document
	if err := wc.DB.Where("uuid = ?", uuid).Preload("User").First(&document).Error; err != nil {
		c.HTML(http.StatusNotFound, "error.html", gin.H{
			"Title":   "文档未找到",
			"Message": "请求的文档不存在或已被删除",
		})
		return
	}

	// 过期检查
	if document.IsExpired() {
		c.HTML(http.StatusOK, "document.html", DocumentViewData{
			Title:     document.Title,
			UUID:      document.UUID,
			IsExpired: true,
		})
		return
	}

	// 私有文档密码检查
	if !document.IsPublic {
		verifiedCookie, err := c.Cookie("document_" + uuid)
		if err != nil || verifiedCookie != "verified" {
			sessionPassword := c.GetHeader("X-Document-Password")
			if !document.CheckPassword(sessionPassword) {
				errorMessage := ""
				if c.Request.Method == http.MethodPost {
					errorMessage = "密码错误，请重试"
				}

				c.HTML(http.StatusOK, "document.html", DocumentViewData{
					Title:            document.Title,
					UUID:             document.UUID,
					ShowPasswordForm: true,
					ErrorMessage:     errorMessage,
				})
				return
			}
		}
	}

	// 增加浏览次数
	if document.IsPublic {
		document.IncrementViews()
		wc.DB.Save(&document)
	} else {
		verifiedCookie, err := c.Cookie("document_" + uuid)
		if err == nil && verifiedCookie == "verified" {
			document.IncrementViews()
			wc.DB.Save(&document)
		}
	}

	frontmatter, contentWithoutFrontmatter := parseFrontmatter(document.Content)

	documentTitle := document.Title
	if frontmatter.Title != "" {
		documentTitle = frontmatter.Title
	}

	contentHTML := renderMarkdown(contentWithoutFrontmatter)

	createdAt := document.CreatedAt.Format("2006-01-02 15:04:05")
	var expiresAt *string
	if document.ExpiresAt != nil {
		expiresStr := document.ExpiresAt.Format("2006-01-02 15:04:05")
		expiresAt = &expiresStr
	}

	templateName := "document.html"
	if document.Theme != "" {
		templateName = "document-" + document.Theme + ".html"
	}

	c.HTML(http.StatusOK, templateName, DocumentViewData{
		Title:       documentTitle,
		UUID:        document.UUID,
		Author:      document.User.Name,
		ContentHTML: contentHTML,
		Views:       document.Views,
		CreatedAt:   createdAt,
		ExpiresAt:   expiresAt,
		IsExpired:   false,
		Theme:       document.Theme,
		UserUID:     document.User.UID,
		Tags:        frontmatter.Tags,
	})
}

// VerifyDocumentPassword 验证文档密码 /d/:uuid/verify
func (wc *WebController) VerifyDocumentPassword(c *gin.Context) {
	uuid := c.Param("uuid")
	password := c.PostForm("password")

	var document models.Document
	if err := wc.DB.Where("uuid = ?", uuid).First(&document).Error; err != nil {
		c.HTML(http.StatusNotFound, "error.html", gin.H{
			"Title":   "文档未找到",
			"Message": "请求的文档不存在或已被删除",
		})
		return
	}

	if document.CheckPassword(password) {
		// 密码正确，设置验证 Cookie
		c.SetCookie("document_"+uuid, "verified", 24*60*60, "/", "", false, true)
		c.Redirect(http.StatusSeeOther, "/d/"+uuid)
	} else {
		c.HTML(http.StatusOK, "document.html", DocumentViewData{
			Title:            document.Title,
			UUID:             document.UUID,
			ShowPasswordForm: true,
			ErrorMessage:     "密码错误，请重试",
		})
	}
}

// DownloadDocument 下载文档 /d/:uuid/download
func (wc *WebController) DownloadDocument(c *gin.Context) {
	uuid := c.Param("uuid")

	var document models.Document
	if err := wc.DB.Where("uuid = ?", uuid).First(&document).Error; err != nil {
		c.HTML(http.StatusNotFound, "error.html", gin.H{
			"Title":   "文档未找到",
			"Message": "请求的文档不存在或已被删除",
		})
		return
	}

	filename := strings.ReplaceAll(document.Title, " ", "_") + ".md"
	c.Header("Content-Disposition", "attachment; filename="+filename)
	c.Header("Content-Type", "text/markdown")
	c.String(http.StatusOK, document.Content)
}

// DocumentList 文档列表页面（按 user_id，用于内部调试）
func (wc *WebController) DocumentList(c *gin.Context) {
	userID := c.Query("user")
	if userID == "" {
		c.HTML(http.StatusBadRequest, "error.html", gin.H{
			"Title":   "参数错误",
			"Message": "需要指定用户参数",
		})
		return
	}

	var documents []models.Document
	if err := wc.DB.Where("user_id = ?", userID).Preload("User").Order("created_at DESC").Find(&documents).Error; err != nil {
		c.HTML(http.StatusInternalServerError, "error.html", gin.H{
			"Title":   "服务器错误",
			"Message": "获取文档列表失败",
		})
		return
	}

	// 构建带目录信息的文档和目录分组
	withDir, dirGroups := buildDirectoryGroups(documents)

	c.HTML(http.StatusOK, "document_list.html", gin.H{
		"Title":           "文档列表",
		"Documents":       withDir,
		"DirectoryGroups": dirGroups,
	})
}

// DocumentListWithSearch 文档列表页面（根据用户 UID，带搜索功能）
// 对应 Laravel DocumentViewController@index 的行为：
// - 参数 user: 用户 UID（16 位）
// - 参数 search: 可选，按标题或内容模糊搜索
func (wc *WebController) DocumentListWithSearch(c *gin.Context) {
	userUID := c.Query("user")
	if userUID == "" {
		c.HTML(http.StatusBadRequest, "error.html", gin.H{
			"Title":   "参数错误",
			"Message": "需要指定用户 UID 参数",
		})
		return
	}

	var user models.User
	if err := wc.DB.Where("uid = ?", userUID).First(&user).Error; err != nil {
		c.HTML(http.StatusNotFound, "error.html", gin.H{
			"Title":   "用户未找到",
			"Message": "指定的用户不存在",
		})
		return
	}

	search := strings.TrimSpace(c.Query("search"))

	query := wc.DB.Model(&models.Document{}).
		Where("user_id = ?", user.ID).
		Where("(expires_at IS NULL OR expires_at > ?)", time.Now())

	if search != "" {
		like := "%" + search + "%"
		query = query.Where("title LIKE ? OR content LIKE ?", like, like)
	}

	var documents []models.Document
	if err := query.Preload("User").Order("created_at DESC").Find(&documents).Error; err != nil {
		c.HTML(http.StatusInternalServerError, "error.html", gin.H{
			"Title":   "服务器错误",
			"Message": "获取文档列表失败",
		})
		return
	}

	// 构建带目录信息的文档和目录分组
	withDir, dirGroups := buildDirectoryGroups(documents)

	c.HTML(http.StatusOK, "document_list.html", gin.H{
		"Title":           "文档列表",
		"Documents":       withDir,
		"DirectoryGroups": dirGroups,
		"User":            user,
		"Search":          search,
	})
}

// Home 首页
func (wc *WebController) Home(c *gin.Context) {
	c.HTML(http.StatusOK, "home.html", gin.H{
		"Title": "Obsidian Share - 分享你的知识",
	})
}

// buildDirectoryGroups 根据文档 Path 构建目录路径和分组数据
func buildDirectoryGroups(docs []models.Document) ([]DocumentWithDir, []DirectoryGroup) {
	withDir := make([]DocumentWithDir, 0, len(docs))
	dirMap := make(map[string][]DocumentWithDir)

	for _, doc := range docs {
		dir := ""
		if doc.Path != "" {
			if idx := strings.LastIndex(doc.Path, "/"); idx > 0 {
				dir = doc.Path[:idx]
			}
		}

		with := DocumentWithDir{
			Document: doc,
			DirPath:  dir,
		}
		withDir = append(withDir, with)

		key := dir
		dirMap[key] = append(dirMap[key], with)
	}

	// 转换为切片并按目录名排序，根目录优先
	dirPaths := make([]string, 0, len(dirMap))
	for k := range dirMap {
		dirPaths = append(dirPaths, k)
	}
	sort.Slice(dirPaths, func(i, j int) bool {
		// 根目录（空字符串）排在最前
		if dirPaths[i] == "" {
			return true
		}
		if dirPaths[j] == "" {
			return false
		}
		return dirPaths[i] < dirPaths[j]
	})

	groups := make([]DirectoryGroup, 0, len(dirPaths))
	for _, dir := range dirPaths {
		groupDocs := dirMap[dir]
		// 同一目录下按标题排序，便于查找
		sort.Slice(groupDocs, func(i, j int) bool {
			return groupDocs[i].Title < groupDocs[j].Title
		})
		groups = append(groups, DirectoryGroup{
			DirPath:   dir,
			Documents: groupDocs,
		})
	}

	return withDir, groups
}
